﻿//////////////////////////////////////////////////////////////////
//
// World.cs
//
// Copyright (c) 2012 Dan Pike. All rights reserved
//
// Refer to license.txt for any license restrictions.
// 
using Entities;
using Entities.Cities;
using Entities.Materials;
using Logger4Net;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Entities
{
   public partial class World : Entity
   {
      ////////////////////////////////////////////////////////////////////////
      //
      public World()
         : base(null)
      {
         world = this;  // can't put 'this' in the base ctor
         try
         {
            // An encyclopedia of objects not tied to a specific situation
            almanac = new Almanac(this);

            //////////////////////////////////////////////////////////////////////////
            //
            cities.Add(aalborg = new Aalborg(this));
            cities.Add(ahus = new Ahus(this));
            cities.Add(bergen = new Bergen(this));
            cities.Add(berlin = new Berlin(this));
            cities.Add(boston = new Boston(this));
            cities.Add(bremen = new Bremen(this));
            cities.Add(breslau = new Breslau(this));
            cities.Add(bruges = new Bruges(this));
            cities.Add(cologne = new Cologne(this));
            cities.Add(danzig = new Danzig(this));
            cities.Add(edinburgh = new Edinburgh(this));
            cities.Add(erfurt = new Erfurt(this));
            cities.Add(flensburg = new Flensburg(this));
            cities.Add(gothenburg = new Gothenburg(this));
            cities.Add(groningen = new Groningen(this));
            cities.Add(haarlem = new Haarlem(this));
            cities.Add(hamburg = new Hamburg(this));
            cities.Add(helsinki = new Helsinki(this));
            cities.Add(kaunas = new Kaunas(this));
            cities.Add(konigsburg = new Konigsburg(this));
            cities.Add(london = new London(this));
            cities.Add(lubeck = new Lubeck(this));
            cities.Add(malmo = new Malmo(this));
            cities.Add(minden = new Minden(this));
            cities.Add(naestved = new Naestved(this));
            cities.Add(nijmegen = new Nijmegen(this));
            cities.Add(novgorod = new Novgorod(this));
            cities.Add(oslo = new Oslo(this));
            cities.Add(posen = new Posen(this));
            cities.Add(reval = new Reval(this));
            cities.Add(riga = new Riga(this));
            cities.Add(ripen = new Ripen(this));
            cities.Add(rostock = new Rostock(this));
            cities.Add(scarborough = new Scarborough(this));
            cities.Add(stavanger = new Stavanger(this));
            cities.Add(stettin = new Stettin(this));
            cities.Add(stockholm = new Stockholm(this));
            cities.Add(thorn = new Thorn(this));
            cities.Add(visby = new Visby(this));
            cities.Add(warsaw = new Warsaw(this));

            cities.ForEach(city =>
               city.factories.Keys.ToList().ForEach(factory =>
                  city.setUtilization(factory, 100)));

            almanac_.materials.ForEach(material => productionDemand_.Add(material, 0.0));

            // Initialize all the journey times
            maxJourneyHash_ = cities.Max(city => city.GetHashCode());
            journeyTimesAalborg.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(aalborg, kvp.Key, kvp.Value));
            journeyTimesAhus.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(ahus, kvp.Key, kvp.Value));
            journeyTimesBergen.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(bergen, kvp.Key, kvp.Value));
            journeyTimesBerlin.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(berlin, kvp.Key, kvp.Value));
            journeyTimesBoston.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(boston, kvp.Key, kvp.Value));
            journeyTimesBremen.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(bremen, kvp.Key, kvp.Value));
            journeyTimesBreslau.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(breslau, kvp.Key, kvp.Value));
            journeyTimesBruges.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(bruges, kvp.Key, kvp.Value));
            journeyTimesCologne.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(cologne, kvp.Key, kvp.Value));
            journeyTimesDanzig.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(danzig, kvp.Key, kvp.Value));
            journeyTimesEdinburgh.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(edinburgh, kvp.Key, kvp.Value));
            journeyTimesErfurt.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(erfurt, kvp.Key, kvp.Value));
            journeyTimesFlensburg.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(flensburg, kvp.Key, kvp.Value));
            journeyTimesGothenburg.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(gothenburg, kvp.Key, kvp.Value));
            journeyTimesGroningen.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(groningen, kvp.Key, kvp.Value));
            journeyTimesHaarlem.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(haarlem, kvp.Key, kvp.Value));
            journeyTimesHamburg.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(hamburg, kvp.Key, kvp.Value));
            journeyTimesHelsinki.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(helsinki, kvp.Key, kvp.Value));
            journeyTimesKaunas.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(kaunas, kvp.Key, kvp.Value));
            journeyTimesKonigsburg.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(konigsburg, kvp.Key, kvp.Value));
            journeyTimesLondon.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(london, kvp.Key, kvp.Value));
            journeyTimesLubeck.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(lubeck, kvp.Key, kvp.Value));
            journeyTimesMalmo.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(malmo, kvp.Key, kvp.Value));
            journeyTimesMinden.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(minden, kvp.Key, kvp.Value));
            journeyTimesNaestved.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(naestved, kvp.Key, kvp.Value));
            journeyTimesNijmegen.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(nijmegen, kvp.Key, kvp.Value));
            journeyTimesNovgorod.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(novgorod, kvp.Key, kvp.Value));
            journeyTimesOslo.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(oslo, kvp.Key, kvp.Value));
            journeyTimesPosen.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(posen, kvp.Key, kvp.Value));
            journeyTimesReval.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(reval, kvp.Key, kvp.Value));
            journeyTimesRiga.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(riga, kvp.Key, kvp.Value));
            journeyTimesRipen.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(ripen, kvp.Key, kvp.Value));
            journeyTimesRostock.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(rostock, kvp.Key, kvp.Value));
            journeyTimesScarborough.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(scarborough, kvp.Key, kvp.Value));
            journeyTimesStavanger.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(stavanger, kvp.Key, kvp.Value));
            journeyTimesStettin.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(stettin, kvp.Key, kvp.Value));
            journeyTimesStockholm.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(stockholm, kvp.Key, kvp.Value));
            journeyTimesThorn.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(thorn, kvp.Key, kvp.Value));
            journeyTimesVisby.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(visby, kvp.Key, kvp.Value));
            journeyTimesWarsaw.OfType<KeyValuePair<City, double>>().All(kvp => setJourneyTime(warsaw, kvp.Key, kvp.Value));

            // dumpIndustry();
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
            throw;
         }
      }

      ////////////////////////////////////////////////////////////////////////
      // Returns an integer that can be used as a hash code for the journey
      // time between two cities. Returns the same number regardless of the
      // order in which the two cities are supplied.
      private int getJourneyHash(City city1, City city2)
      {
         int journeyHash = 0;
         try
         {
            int hash1 = (int)city1.GetHashCode();
            int hash2 = (int)city2.GetHashCode();
            if (hash1 != hash2)
            {
               journeyHash = (hash1 < hash2)
                  ? hash1 + (maxJourneyHash_ * hash2)
                  : hash2 + (maxJourneyHash_ * hash1);
            }
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return journeyHash;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      private bool setJourneyTime(City city1, City city2, double journeyTime)
      {
         try
         {
            journeyTimes_.Add(getJourneyHash(city1, city2), journeyTime);
            return true;
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public double getJourneyTime(City city1, City city2)
      {
         int hashCode = 0;
         double journeyTime = 0.0;
         try
         {
            if (city1 != city2)
            {
               hashCode = getJourneyHash(city1, city2);
               journeyTime = journeyTimes_[hashCode];
            }
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return journeyTime;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public override bool dirty
      {
         get
         {
            return base.dirty || cities.Any(city => city.dirty) ||
               convoys.Values.Any(convoy => convoy.dirty);
         }
         set
         {
            if (value)
            {
               base.dirty = value;
            }
            cities.ForEach(city => city.dirty = value);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public void dumpIndustry()
      {
         try
         {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("current industry structure:");
            cities.ForEach(city =>
               {
                  sb.AppendLine(string.Format("   city: {0}", city.name));
                  city.factories.All(kvp =>
                     {
                        Factory factory = kvp.Key;
                        sb.AppendLine(
                           string.Format("      factory: {0} x {1}", factory.name, kvp.Value)
                           );
                        return true;
                     });
               });
            logger_.debug(sb.ToString());
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public void clear()
      {
         try
         {
            population_ = 0;
            productionDemand_.Keys.ToList().ForEach(demand => productionDemand_[demand] = 0.0);
            convoys.Clear();
            cities.ForEach(city => city.clear());
            filename_ = string.Empty;
            dirty = false;
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public bool load(string filename)
      {
         try
         {
            clear();
            archive_ = Utilities.Archive.create<Archive.WorldArchive>(Archive.WorldArchive.CONFIG_SECTION, filename);
            if (!cities.All(city => city.load(filename, archive_.cities[city.name])))
            {
               return false;
            }
            if (!loadConvoys())
            {
               return false;
            }
            filename_ = Path.GetFullPath(filename);
            recalculate();       // recalculate the demands from population and industry
            dirty = false;
            return true;
         }
         catch (System.Exception ex)
         {
            logger_.exception("load({0})", ex, filename);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public bool loadConvoys()
      {
         Dictionary<string, Convoy> saveConvoys = world.convoys;
         try
         {
            convoys = new Dictionary<string, Convoy>(StringComparer.CurrentCultureIgnoreCase);
            if (archive_.convoys.OfType<Archive.Convoy>().All(archiveConvoy =>
               {
                  // The load() operation also puts it into the world.convoys collection
                  Convoy convoy = new Convoy(this);
                  bool result = convoy.load(archiveConvoy);
                  saveConvoys.Add(convoy.name, convoy);
                  return result;
               }))
            {
               return true;
            }
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         // Revert to the original definitions
         world.convoys = saveConvoys;
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public bool writeArchive()
      {
         try
         {
            return cities.All(city => city.saveAs(filename, archive_))
               && writeArchiveConvoys();
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public bool writeArchiveConvoys()
      {
         try
         {
            archive_.convoys.Clear();
            return convoys.Values.All(qConvoy =>
               {
                  try
                  {
                     Archive.Convoy archiveConvoy = new Archive.Convoy();
                     archiveConvoy.name = qConvoy.name;
                     archive_.convoys.Add(archiveConvoy);
                     qConvoy.write(archiveConvoy);
                     return true;
                  }
                  catch (System.Exception ex)
                  {
                     logger_.exception(ex);
                  }
                  return false;
               });
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public bool save()
      {
         try
         {
            if (writeArchive() && archive_.save())
            {
               dirty = false;
               return true;
            }
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public bool saveAs(string filename)
      {
         try
         {
            if (null == archive_)
            {
               archive_ = Utilities.Archive.create<Archive.WorldArchive>(Archive.WorldArchive.CONFIG_SECTION, filename);
            }
            if (writeArchive() && archive_.saveAs(Archive.WorldArchive.CONFIG_SECTION, filename))
            {
               filename_ = Path.GetFullPath(filename);
               dirty = false;
               return true;
            }
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public Almanac almanac
      {
         get { return almanac_; }
         internal set { almanac_ = value; }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public string filename
      {
         get { return filename_; }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public int population
      {
         get { return population_; }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public double getProductionDemand(Material material)
      {
         try
         {
            double result = 0.0;
            if (productionDemand_.TryGetValue(material, out result))
            {
               return result;
            }
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return 0.0;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public double getWarehouse(Material material)
      {
         try
         {
            if (null == material)
            {
               return cities.Sum(city => almanac.materials.Sum(M => city.getWarehouse(M)));
            }
            return cities.Sum(city => city.getWarehouse(material));
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return 0.0;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public void recalculate()
      {
         try
         {
            // Reset the values
            almanac_.materials.ForEach(material => productionDemand_[material] = 0.0);

            // And recalculate demands for each of the cities, building the world totals as we go
            population_ = 0;
            cities.ForEach(city =>
               {
                  population_ += city.population;

                  // Recalculate all of the local settings based on the city's population
                  // and distance from its hub
                  city.recalculate();

                  // Add the city demands for all of the materials to our global demand
                  // map
                  almanac_.materials.ForEach(material =>
                     productionDemand_[material] += city.getProductionDemand(material)
                     );
               });
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
      }

      //////////////////////////////////////////////////////////////////////////
      // All of the cities
      public List<City> cities = new List<City>();
      public Aalborg aalborg;
      public Ahus ahus;
      public Bergen bergen;
      public Berlin berlin;
      public Boston boston;
      public Bremen bremen;
      public Breslau breslau;
      public Bruges bruges;
      public Cologne cologne;
      public Danzig danzig;
      public Edinburgh edinburgh;
      public Erfurt erfurt;
      public Flensburg flensburg;
      public Gothenburg gothenburg;
      public Groningen groningen;
      public Haarlem haarlem;
      public Hamburg hamburg;
      public Helsinki helsinki;
      public Kaunas kaunas;
      public Konigsburg konigsburg;
      public London london;
      public Lubeck lubeck;
      public Malmo malmo;
      public Minden minden;
      public Naestved naestved;
      public Nijmegen nijmegen;
      public Novgorod novgorod;
      public Oslo oslo;
      public Posen posen;
      public Reval reval;
      public Riga riga;
      public Ripen ripen;
      public Rostock rostock;
      public Scarborough scarborough;
      public Stavanger stavanger;
      public Stettin stettin;
      public Stockholm stockholm;
      public Thorn thorn;
      public Visby visby;
      public Warsaw warsaw;

      ////////////////////////////////////////////////////////////////////////
      //
      public Dictionary<string, Convoy> convoys = new Dictionary<string, Convoy>(StringComparer.CurrentCultureIgnoreCase);

      ////////////////////////////////////////////////////////////////////////
      //
      private string filename_;
      private Archive.WorldArchive archive_;
      private Almanac almanac_;

      // Things that are updating when we do a recalculate
      private Dictionary<Material, double> productionDemand_ = new Dictionary<Material, double>();
      private int population_;

      // Journey times are stored as half the round-trip time between two cities
      // Given two cities (city1, city2), form the key as follows:
      //
      //    
      private int maxJourneyHash_;
      private Dictionary<int, double> journeyTimes_ = new Dictionary<int, double>();
   }
}
